/*
 Copyright (c) Whitemice Consulting, 2007, 2008, 2009
 Adam Tauno Williams <awilliam@whitemice.org>
 License: MIT/X11

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using CookComputing.XmlRpc;
using Whitemice.ZOGI.RPC2;

namespace Whitemice.ZOGI.Backend
{
  /// <summary>
  /// The Factory is used to process the results of an RPC request, either creating
  /// new representatal objects (Contact, Enterprise, etc...) of the entities
  /// represented in the results or marshalling corresponding objects from the cache
  /// and performing an update.  The Factory expects "clean" RPC structures as
  /// produced by the Whitemice.ZOGI.RPC2.RpcWasher
  /// </summary>
  public class Factory
  {   
    protected Server                              server;
    protected IZOGICommand                        command;
    protected Cache                               cache;
    
    public Factory(IZOGICommand c)
    {
      server = Server.Connection;
      cache = Server.Cache;
      command = c;
    } // end Factory
    
    public IEnumerable<Whitemice.ZOGI.Entity> ResultsFromArray(Array i)
    {
      IList<Whitemice.ZOGI.Entity>       results;
      Whitemice.ZOGI.Entity              entity;

      results = new List<Whitemice.ZOGI.Entity>(i.Length);
      foreach(IDictionary<string,object> x in RpcWasher.Process(i))
        if ((entity = Manifest(x)) != null)
          results.Add(entity);
      return results;
    } // end Results
    
    public IZOGICommand Command { get { return command; } } 
    
    public Whitemice.ZOGI.Entity ResultFromStruct(XmlRpcStruct s)
    {
      IDictionary<string,object>  i;

      this.ZLOG("ResultFromStruct");
      i = RpcWasher.Process(s);
      if (command is Whitemice.ZOGI.IZOGIPutCommand)
      { 
        IZOGIPutCommand put = (command as IZOGIPutCommand);
        if (put.Entity.FirstClass)
        {
          put.Entity.Take(i, put.DetailLevel);
          if (put.Creation)
            Server.Cache.TransferId(put.PreviousObjectId, put.Entity);
        } else
          {
            Server.Connection.Get(put.Entity.ParentObjectId, 65535, true);
          }
        return put.Entity;
      }
      return Manifest(i);
    } // end SingleResult
    
    protected Entity Manifest(IDictionary<string,object> x)
    {
      string entityName;
      Entity result = null;    
    
      entityName = ZOGIDecoder.AsString(x, "entityName");
      switch(entityName)
      {
        case "PluginData":
          server.PluginDataHandler(x as Hashtable);
         break;
        case "ApplicationAlert":
          server.AlertHandler(x as Hashtable);
         break;
        case "UserAlert":
          server.UserMessageHandler(ZOGIDecoder.AsString(x, "message"));
         break;
        case "Unknown":
          UnknownEntity(ZOGIDecoder.AsInt32(x, "objectId"));
         break;
        default:
          // Make the entity via the factory 
          result = ManifestEntity(x);
         break;
      } // end switch
      if (result == null)
        return null;
      if (result.FirstClass)
      {
        int c = 0;
        
        foreach(string key in x.Keys)
          if (key.StartsWith("_"))
          {
            IEnumerable  i;
            Entity       e;
            
            c++;
            this.ZLOG(string.Format("Object has subordinate key {0}", key));
            result.ClearSubordinatesOfKey(key);
            i = (x[key] as IEnumerable<IDictionary<string,object>>);
            foreach(IDictionary<string,object> s in i)
            {
              e = ManifestEntity(s, 0);
              if (e == null)
                continue;
              this.ZLOG(string.Format("Subordinate object is {0}", e.GetType()));
              if ((e is Whitemice.ZOGI.Assignment) ||
                  (e is Whitemice.ZOGI.ACL))
              {
                int targetId = (e as Whitemice.ZOGI.ObjectRelation).TargetId;
                this.ZLOG(string.Format("Storing subordinate target {0}", targetId));
                result.AddSubordinate(key, targetId);
              } // end if assignment
            } // end foreach
          } // end if-subordinate-key
      } // end is FirstClass
      return result;
    } // end Manifest

    protected Entity ManifestEntity(IDictionary<string,object> x)
    {
      return ManifestEntity(x, command.DetailLevel);
    }
    
    protected Entity ManifestEntity(IDictionary<string,object> x, int d)
    {
      int     objectId = 0;
      int     version  = 0;
      Entity  result   = null;
      string  entityName;

      this.ZLOG("ManifestEntity[entityName={0}]", x["entityName"]);
      if (x.ContainsKey("objectId"))
      {
        objectId = (int)x["objectId"];
        if ((result = Server.Cache.Get(objectId)) == null)
        {
          this.ZLOG("no matching cached object");
          // new object
          if(InitializeEntity(x, d, out result))
          {
            // do not store assignment or ACL entities.
            if (!((result is Whitemice.ZOGI.Assignment)) ||
                  (result is Whitemice.ZOGI.ACL))
              Server.Cache.Store(result);
          } 
        } else
          {
            this.ZLOG("cached object found");
            // object cached, check if update required
	          if (x.ContainsKey("version"))
	            version = (int)x["version"];
	          entityName = (string)x["entityName"];
	          if (((version == 0) || 
	               (version > result.Version)) &&
	              (!((entityName == "logEntry") || 
	                 (entityName == "taskNotation"))))
	          {
	            // Update the entity from the structure
	            result.Take(x, command.DetailLevel);
	          }
          }
      } else
        {
          // entity does not have an objectId
          // handle non-well-formed entities here
        }
      return result;
    } // end ManifestEntity
    
    protected void UnknownEntity(int objectId)
    {
      // delete object with this id from cache
    }

    protected bool InitializeEntity(IDictionary<string,object> x,
                                    out Entity result)
    {
      return InitializeEntity(x, command.DetailLevel, out result);
    }
    
    protected bool InitializeEntity(IDictionary<string,object> x,
                                    int d,
                                    out Entity result)
    {
      string entityName;
      bool   creation = true;
      
      result = null;
      entityName  = (string)x["entityName"];
      this.ZLOG(string.Format("Factory initializing {0} entity", entityName));
      switch(entityName)
      {
	  		case "Account": result = new Account(x, d); break;
	  		case "address": result = new Address(x, d); break;
	  		case "assignment": result = new Assignment(x, d); break;
	  		case "Appointment": result = new Appointment(x, d); break;
	  		case "File": result = new BLOB(x, d); break;
	  		case "Contact": result = new Contact(x, d); break;
	  		case "Enterprise": result = new Enterprise(x, d); break;
	  		case "Folder": result = new Folder(x, d); break;
	  		case "logEntry": result = new AuditEntry(x, d); break;
	  		case "note": result = new Note(x, d); break;
	  		case "participant": result = new Participant(x, d); break;
	  		case "Project":  result = new Project(x, d); break;
	  		case "Resource": result = new Resource(x, d); break;
	  		case "Task": result = new Task(x, d); break;
	  		case "taskNotation": result = new TaskNotation(x, d); break;
	  		case "telephone": result = new PhoneNumber(x, d); break;
	  		case "Team": result = new Team(x, d); break;
	  		/* case "notification": result = new Notification(x, d); break; */
	  	  case "Unknown": break;
	  		case "companyValue": 
	  			result = cache.LegacyXAFromCache(
	  								(x["attribute"] as System.String),
	  								(int)x["companyObjectId"]);
	  			if (result == null)
	  			{
	  			  result = new CompanyValue(x, d);
	  			} else 
	  			  {
	  			    result.Take(x, d);
	  			    creation = false;
	  			  }
	  			break;
	  		case "objectLink": 
	  			  result = new ObjectLink(x, d);
	  			break;
	  	  case "defaults": break;
	  		default:
	  			Console.WriteLine("Unrecognized entity {0}", entityName);
	  			break;
      }
      return creation;
    } // end InitializeEntity
  }
}
